#!/usr/bin/python
# Copyright 2020 Makani Technologies LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


"""Generate the aio_node.[ch] files."""

import os
import re
import sys
import textwrap

from makani.avionics.network import network_config
from makani.avionics.network import node_locations
from makani.lib.python import string_util


def _WriteAioNodeHeader(script_name, file_without_extension, rel_path, config,
                        locations):
  """Write aio_node.h.

  Args:
    script_name: This script's filename.
    file_without_extension: The full path to the output file, missing the '.h'.
    rel_path: The relative path from the autogenerated files root to
        file_without_extension.
    config: A NetworkConfig
    locations: The location:nodes dict from node_locations.
  """
  header_guard = re.sub('[/.]', '_', rel_path.upper()) + '_H_'
  parts = [textwrap.dedent("""
      #ifndef {guard}
      #define {guard}

      // Generated by {name}; do not edit.

      #include <stdbool.h>

      #ifdef __cplusplus
      extern "C" {{
      #endif

      typedef enum {{
        kAioNodeForceSigned = -1,"""[1:]).format(name=script_name,
                                                 guard=header_guard)]
  for node in config.aio_nodes:
    if node.snake_name == 'unknown':
      parts.append('  kAioNodeUnknown = 0,  // Reserved.  Default AioNode '
                   'for invalid configuration.')
    else:
      parts.append('  %s = %d,' % (node.enum_name, node.enum_value))

  parts.append(textwrap.dedent("""\
        kNumAioNodes
      } AioNode;

      const char *AioNodeToString(AioNode node);
      const char *AioNodeToShortString(AioNode node);
      AioNode StringToAioNode(const char *str);
      bool IsQ7Node(AioNode node);
      bool IsTms570Node(AioNode node);
      bool IsValidNode(AioNode node);"""))

  for location in locations:
    parts.append('bool Is{location}Node(AioNode node);'.format(
        location=string_util.SnakeToCamel(location)))

  for label in config.aio_labels:
    parts.append('bool Is%sNode(AioNode node);'
                 % string_util.SnakeToCamel(label))

  parts.append(textwrap.dedent("""
      #ifdef __cplusplus
      }}  // extern "C"
      #endif

      #endif  // {guard}
      """).format(guard=header_guard))

  with open(file_without_extension + '.h', 'w') as f:
    f.write('\n'.join(parts))


def _WriteAioNodeSource(script_name, file_without_extension, rel_path,
                        config, locations):
  """Write aio_node.c.

  Args:
    script_name: This script's filename.
    file_without_extension: The full path to the output file, missing the '.c'.
    rel_path: The relative path from the autogenerated files root to our output
              file.
    config: A NetworkConfig.
    locations: The location:nodes dict from node_locations.
  """
  parts = [textwrap.dedent("""
      // Generated by {name}; do not edit.

      #include "{header}"

      #include <assert.h>
      #include <stdbool.h>
      #include <string.h>

      #include "common/macros.h"

      const char *AioNodeToString(AioNode node) {{
        switch (node) {{
          // Fall-through intentional.
          default:
          case kAioNodeForceSigned:
          case kNumAioNodes:
            assert(false);
            return "<error>";"""[1:]).format(name=script_name,
                                             header=rel_path + '.h')]

  for node in config.aio_nodes:
    parts.append('    case %s:' % node.enum_name)
    parts.append('      return "%s";' % node.enum_name)

  parts.append(textwrap.dedent("""
        }
      }

      const char *AioNodeToShortString(AioNode node) {
        if (0 <= node && node < kNumAioNodes) {
          return AioNodeToString(node) + ARRAYSIZE("kAioNode") - 1;
        } else {
          return "<error>";
        }
      }

      AioNode StringToAioNode(const char *str) {
        for (AioNode node = 0; node < kNumAioNodes; ++node) {
          const char *name = AioNodeToString(node);
          if (name != NULL && !strcmp(str, name)) {
            return node;
          }
          name = AioNodeToShortString(node);
          if (name != NULL && !strcmp(str, name)) {
            return node;
          }
        }
        return kAioNodeForceSigned;
      }"""[1:]))

  parts.append(textwrap.dedent("""
      bool IsQ7Node(AioNode node) {
        switch (node) {"""))
  for node in config.aio_nodes:
    parts.append('    case %s:' % node.enum_name)
    parts.append('      return %s;' %
                 ('true' if node.q7_node else 'false'))

  parts.append(textwrap.dedent("""
          case kAioNodeForceSigned:
          case kNumAioNodes:
          default:
            return false;
        }
      }

      bool IsTms570Node(AioNode node) {
        switch (node) {"""[1:]))
  for node in config.aio_nodes:
    parts.append('    case %s:' % node.enum_name)
    parts.append('      return %s;' %
                 ('true' if node.tms570_node else 'false'))

  parts.append(textwrap.dedent("""
          case kAioNodeForceSigned:
          case kNumAioNodes:
          default:
            return false;
        }
      }

      bool IsValidNode(AioNode node) {
        return IsTms570Node(node) || IsQ7Node(node);
      }"""[1:]))

  for label in config.aio_labels:
    instances = config.GetAioNodesByLabel(label)
    if len(instances) == 1:
      parts.append(textwrap.dedent("""
          bool Is{kind}Node(AioNode node) {{
            return node == {node};
          }}""").format(kind=string_util.SnakeToCamel(label),
                        node=instances[0].enum_name))
    else:
      parts.append(textwrap.dedent("""
          bool Is{kind}Node(AioNode node) {{
            return (node >= {first}) &&
                   (node <= {last});
          }}""").format(kind=string_util.SnakeToCamel(label),
                        first=instances[0].enum_name,
                        last=instances[-1].enum_name))

  for location_name, location_nodes in locations.iteritems():
    is_map = [str(node in location_nodes).lower() for node in config.aio_nodes]
    map_str = '    ' + '\n    '.join(textwrap.wrap(', '.join(is_map), 76))
    parts.append(textwrap.dedent("""
        bool Is{location}Node(AioNode node) {{
          const bool kMap[kNumAioNodes] = {{
        {map}
          }};
          return (0 <= node && node < kNumAioNodes) ? kMap[node] : false;
        }}""").format(location=string_util.SnakeToCamel(location_name),
                      map=map_str))

  with open(file_without_extension + '.c', 'w') as f:
    f.write('\n'.join(parts) + '\n')


def _WriteAioNode(autogen_root, output_dir, script_name, config, locations):
  """Write aio_node.[ch].

  Args:
    autogen_root: The MAKANI_HOME-equivalent top directory.
    output_dir: The directory in which to output the files.
    script_name: This script's filename.
    config: A NetworkConfig.
    locations: The location:nodes dict from node_locations.
  """

  file_without_extension = os.path.join(output_dir, 'aio_node')
  rel_path = os.path.relpath(file_without_extension, autogen_root)

  _WriteAioNodeHeader(script_name, file_without_extension, rel_path, config,
                      locations)
  _WriteAioNodeSource(script_name, file_without_extension, rel_path, config,
                      locations)


def main(argv):
  flags, argv = network_config.ParseGenerationFlags(argv)

  config = network_config.NetworkConfig(flags.network_file)
  script_name = os.path.basename(argv[0])
  locations = node_locations.GetNodeLocations(config)
  _WriteAioNode(flags.autogen_root, flags.output_dir, script_name, config,
                locations)


if __name__ == '__main__':
  main(sys.argv)
